package com.aptemo.webetek.server.datastore;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;

import org.datanucleus.store.appengine.query.JDOCursorHelper;

import com.aptemo.webetek.client.enums.EnumBetStatus;
import com.aptemo.webetek.client.enums.EnumBetType;
import com.aptemo.webetek.client.enums.EnumEventStatus;
import com.aptemo.webetek.client.enums.EnumEventType;
import com.aptemo.webetek.client.enums.EnumPaymentOrdering;
import com.aptemo.webetek.client.enums.EnumPaymentType;
import com.aptemo.webetek.client.enums.EnumPickStatus;
import com.aptemo.webetek.client.enums.EnumTeamOrdering;
import com.aptemo.webetek.client.enums.EnumTeamStatus;
import com.aptemo.webetek.client.enums.EnumUserOrdering;
import com.aptemo.webetek.client.enums.EnumUserRole;
import com.aptemo.webetek.client.enums.EnumUserStatus;
import com.aptemo.webetek.client.exceptions.BackendException;
import com.aptemo.webetek.client.exceptions.BackendException.EErrorCodes;
import com.aptemo.webetek.server.CommonService;
import com.aptemo.webetek.server.data.Bet;
import com.aptemo.webetek.server.data.BetLabel;
import com.aptemo.webetek.server.data.PMF;
import com.aptemo.webetek.server.data.Payment;
import com.aptemo.webetek.server.data.Pick;
import com.aptemo.webetek.server.data.Stake;
import com.aptemo.webetek.server.data.UserGroup;
import com.aptemo.webetek.server.data.WebetekUser;
import com.aptemo.webetek.shared.BetBean;
import com.aptemo.webetek.shared.EventBean;
import com.aptemo.webetek.shared.LabelBean;
import com.aptemo.webetek.shared.PagingResult;
import com.aptemo.webetek.shared.PaymentBean;
import com.aptemo.webetek.shared.PickBean;
import com.aptemo.webetek.shared.TeamBean;
import com.aptemo.webetek.shared.UserBean;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class DataStoreImpl implements DataStoreAPI
{

	protected DatastoreService	service;
	protected CommonService		utils					= new CommonService();

	private static final long	GAME_TIME_FUSE_CONST	= 900000;				// 15
	private static final int	MAX_PICKS				= 10;

	private DatastoreService getService()
	{
		if (service == null)
		{
			service = DatastoreServiceFactory.getDatastoreService();
		}
		return service;
	}

	private UserGroup extractTeam(String teamId, PersistenceManager pm) throws BackendException
	{
		if (null == teamId || teamId.isEmpty())
		{
			throw new BackendException(EErrorCodes.INVALID_ID);
		}

		Key key = KeyFactory.stringToKey(teamId);
		if (null == key)
		{
			throw new BackendException(EErrorCodes.INVALID_ID);
		}

		UserGroup team = pm.getObjectById(UserGroup.class, key);
		return team;
	}

	private WebetekUser extractUser(String userId, PersistenceManager pm) throws BackendException
	{
		if (null == userId || userId.isEmpty())
		{
			throw new BackendException(EErrorCodes.INVALID_ID);
		}

		Key key = KeyFactory.stringToKey(userId);
		if (null == key)
		{
			throw new BackendException(EErrorCodes.INVALID_ID);
		}

		WebetekUser user = pm.getObjectById(WebetekUser.class, key);
		return user;
	}

	private UserBean changeTeamOwner(String email, UserGroup team) throws BackendException
	{

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			// UserGroup team = extractTeam(teamId, pm);

			WebetekUser user = null;

			if (email == null || email.isEmpty())
			{
				throw new BackendException(EErrorCodes.INVALID_OWNER_DATA);
			}

			// TODO efficiency????
			// try query... Update UserGrou where...
			// check if owner is one of the team players
			List<WebetekUser> players = team.getPlayers();
			for (WebetekUser player : players)
			{
				// WebetekUser player = pm.getObjectById(WebetekUser.class,
				// item);

				// revert status
				if (player.getRole() == EnumUserRole.EDITOR)
				{
					player.setRole(EnumUserRole.GAMER);
				}
				if (player.getEmail().equals(email))
				{
					user = player;
					user.setRole(EnumUserRole.EDITOR);
					team.setOwnerKey(user.getKey(), user.getEmail());
				}

			}
			// still null? create new player/owner
			if (user == null)
			{
				UserBean ub = this.createPlayer(team.getSid(), email, EnumUserStatus.ACTIVE, EnumUserRole.EDITOR, 0.0);
				team.setOwnerKey(KeyFactory.stringToKey(ub.getKey()), email);
				return ub;
			}

			return user.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public TeamBean getTeam(String teamId) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			UserGroup group = extractTeam(teamId, pm);
			return group.toBean();
		}
		finally
		{
			pm.close();
		}

	}

	@Override
	public EventBean getEvent(String gameId)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(gameId);
			Bet bet = pm.getObjectById(Bet.class, key);

			return bet.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public BetBean getBet(String id) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(id);
			Stake obj = pm.getObjectById(Stake.class, key);
			return obj.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public UserBean getPlayer(String userid) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			WebetekUser user;
			Key key = KeyFactory.stringToKey(userid);
			user = pm.getObjectById(WebetekUser.class, key);
			if (user == null)
			{
				throw new BackendException(EErrorCodes.NOT_WEBETEK_USER);
			}
			UserBean bean = user.toBean();
			return bean;
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public PaymentBean getPayment(String id) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(id);
			Payment payment = pm.getObjectById(Payment.class, key);
			return payment.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public LabelBean getLabel(String id) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(id);
			BetLabel label = pm.getObjectById(BetLabel.class, key);
			return label.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public PagingResult<EventBean> getEvents(String teamId, String labelId, Date treshold, EnumEventStatus status, String cursorString, int pageSize)
			throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(Bet.class);
		try
		{

			Key labelkey = null;
			if (labelId != null && !labelId.isEmpty())
			{
				labelkey = KeyFactory.stringToKey(labelId);
			}
			//
			String filterString = "parentKey == :p1";
			if ((status == EnumEventStatus.ALL) || (status == null))
			{
			}
			else
			{
				filterString += " && status == '" + status.toString() + "'";
			}
			if (labelkey != null)
			{
				filterString += " && labels == :p2";
			}
			if (treshold != null)
			{
				filterString += " && date >= :p3";
			}

			query.setOrdering("date asc");
			query.setRange(0, pageSize);
			// paging...
			int totalCount = -1;
			if (cursorString != null && !cursorString.isEmpty())
			{
				Cursor cursor = Cursor.fromWebSafeString(cursorString);
				Map<String, Object> extensionMap = new HashMap<String, Object>();
				extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);
				query.setExtensions(extensionMap);
			}
			else
			{
				// query for a size (only 1st time)
				Query sizeq = pm.newQuery("select id from " + Bet.class.getName());
				sizeq.setFilter(filterString);
				List<Key> keys = (List<Key>) sizeq.execute(KeyFactory.stringToKey(teamId), labelkey, treshold);
				//
				totalCount = keys.size();
				sizeq.closeAll();
			}
			query.setFilter(filterString);

			List<Bet> qres = (List<Bet>) query.execute(KeyFactory.stringToKey(teamId), labelkey, treshold);

			// paging...
			String newCursorString = "";
			if (qres.size() == pageSize)
			{
				Cursor cursor = JDOCursorHelper.getCursor(qres);
				newCursorString = cursor.toWebSafeString();
			}

			// Get a list
			Date currentTime = new Date(System.currentTimeMillis());
			ArrayList<EventBean> games = new ArrayList<EventBean>();
			for (Bet bet : qres)
			{
				if (bet.getDate().before(currentTime))
				{
					if (bet.getStatus() == EnumEventStatus.ACTIVE)
					{
						bet.setStatus(EnumEventStatus.WAITINGRESULTS);
					}
				}
				EventBean betbean = bet.toBean();
				games.add(betbean);
			}

			return new PagingResult<EventBean>(games, newCursorString, totalCount);
		}
		// catch (IllegalArgumentException e)
		// {
		// throw new BackendException(EErrorCodes.INVALID_ID);
		// }
		finally
		{
			query.closeAll();

			pm.close();
		}

	}

	@SuppressWarnings("unchecked")
	@Override
	public ArrayList<EventBean> getEvents(String teamId, String labelId, Date treshold, EnumEventStatus status) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(Bet.class);
		try
		{

			Key labelKey = null;
			if (labelId != null && !labelId.isEmpty())
			{
				labelKey = KeyFactory.stringToKey(labelId);
			}

			String filterString = "parentKey == :p1";
			if ((status == EnumEventStatus.ALL) || (status == null))
			{
			}
			else
			{
				filterString += " && status == '" + status.toString() + "'";
			}
			if (labelKey != null)
			{
				filterString += " && labels == :p2";
			}
			if (treshold != null)
			{
				filterString += " && date >= :p3";
			}
			query.setFilter(filterString);
			query.setOrdering("date asc");

			List<Bet> qbets = (List<Bet>) query.execute(KeyFactory.stringToKey(teamId), labelKey, treshold);

			// Get a list
			ArrayList<EventBean> bets = new ArrayList<EventBean>();

			Date currentTime = new Date(System.currentTimeMillis());
			for (Bet bet : qbets)
			{
				if (bet.getDate().before(currentTime))
				{
					if (bet.getStatus() == EnumEventStatus.ACTIVE)
					{
						bet.setStatus(EnumEventStatus.WAITINGRESULTS);
					}
				}
				EventBean betbean = bet.toBean();
				bets.add(betbean);
			}
			return bets;
		}
		catch (IllegalArgumentException e)
		{
			throw new BackendException(EErrorCodes.INVALID_ID);
		}
		finally
		{
			query.closeAll();
			pm.close();
		}
	}

	@Override
	public PagingResult<UserBean> getPlayers(String teamId, EnumUserStatus status, EnumUserOrdering order, String cursor, int pageSize) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(WebetekUser.class);
		try
		{
			// UserGroup team = extractTeam(teamId, pm);
			Key teamKey = KeyFactory.stringToKey(teamId);

			query.setRange(0, pageSize);

			String filterString = "parentKey == :p1";
			if ((status == EnumUserStatus.ALL) || (status == null))
			{
				// filterString +=
				// " && (status == INVITED || status == ACTIVE || status == CREATED || status == JOINING)";
			}
			else
			{
				filterString += " && status == '" + status.toString() + "'";
			}
			// paging...
			int totalCount = -1;
			if (cursor != null && !cursor.isEmpty())
			{
				Cursor curs = Cursor.fromWebSafeString(cursor);
				Map<String, Object> extensionMap = new HashMap<String, Object>();
				extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, curs);
				query.setExtensions(extensionMap);
			}
			else
			{
				// query for a size (only 1st time)
				Query sizeq = pm.newQuery("select key from " + WebetekUser.class.getName());
				sizeq.setFilter(filterString);
				List<Key> keys = (List<Key>) sizeq.execute(teamKey);
				totalCount = keys.size();
				sizeq.closeAll();
			}
			query.setFilter(filterString);
			switch (order)
			{
				case DATE_ASC:
					query.setOrdering("created asc");
					break;
				case DATE_DESC:
					query.setOrdering("created desc");
					break;
				case EARNINGS_ASC:
					query.setOrdering("earnings asc");
					break;
				case EARNINGS_DESC:
					query.setOrdering("earnings desc");
					break;
				case BALANCE_ASC:
					query.setOrdering("balance asc");
					break;
				case BALANCE_DESC:
					query.setOrdering("balance desc");
					break;
				case WON_ASC:
					query.setOrdering("wonBetCnt asc");
					break;
				case WON_DESC:
					query.setOrdering("wonBetCnt desc");
					break;
				case TOTAL_ASC:
					query.setOrdering("totalBetCnt asc");
					break;
				case TOTAL_DESC:
					query.setOrdering("totalBetCnt desc");
					break;
				default:
					query.setOrdering("created asc");
			}

			List<WebetekUser> qres = (List<WebetekUser>) query.execute(teamKey);

			// paging...
			String newCursorString = "";
			if (qres.size() == pageSize)
			{
				Cursor curs = JDOCursorHelper.getCursor(qres);
				newCursorString = curs.toWebSafeString();
			}

			// convert to client friendly bean
			ArrayList<UserBean> userbeans = new ArrayList<UserBean>();
			for (WebetekUser item : qres)
			{
				userbeans.add(item.toBean());
			}

			return new PagingResult<UserBean>(userbeans, newCursorString, totalCount);
		}
		finally
		{
			query.closeAll();
			pm.close();
		}
	}

	@Override
	public ArrayList<UserBean> getPlayers(String teamId, EnumUserStatus status, EnumUserOrdering order) throws BackendException
	{
		ArrayList<UserBean> gamersbean = new ArrayList<UserBean>();

		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(WebetekUser.class);
		try
		{
			// get gamers...

			String filterString = "parentKey == :p1";
			if ((status == EnumUserStatus.ALL) || (status == null))
			{
				// filterString +=
				// " && (status == INVITED || status == ACTIVE || status == CREATED || status == JOINING)";
			}
			else
			{
				filterString += " && status == '" + status.toString() + "'";
			}
			query.setFilter(filterString);

			if (order == null)
			{
				order = EnumUserOrdering.DATE_ASC;
			}
			switch (order)
			{
				case DATE_ASC:
					query.setOrdering("created asc");
					break;
				case DATE_DESC:
					query.setOrdering("created desc");
					break;
				case EARNINGS_ASC:
					query.setOrdering("earnings asc");
					break;
				case EARNINGS_DESC:
					query.setOrdering("earnings desc");
					break;
				case BALANCE_ASC:
					query.setOrdering("balance asc");
					break;
				case BALANCE_DESC:
					query.setOrdering("balance desc");
					break;
				case WON_ASC:
					query.setOrdering("wonBetCnt asc");
					break;
				case WON_DESC:
					query.setOrdering("wonBetCnt desc");
					break;
				case TOTAL_ASC:
					query.setOrdering("totalBetCnt asc");
					break;
				case TOTAL_DESC:
					query.setOrdering("totalBetCnt desc");
					break;
				default:
					query.setOrdering("created asc");
			}
			//
			Key teamKey = KeyFactory.stringToKey(teamId);
			List<WebetekUser> qres = (List<WebetekUser>) query.execute(teamKey);

			for (WebetekUser item : qres)
			{
				gamersbean.add(item.toBean());
			}

			return gamersbean;
		}
		finally
		{
			query.closeAll();
			pm.close();
		}
	}

	@Override
	public ArrayList<TeamBean> getTeams(String nameFilter, boolean publicity, EnumTeamOrdering order) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(UserGroup.class);
		try
		{
			String filterString = "isPublic == :p1";
			filterString += " && status == '" + EnumTeamStatus.ACTIVE + "'";
			query.setFilter(filterString);
			if (order == null)
			{
				order = EnumTeamOrdering.DATE_ASC;
			}
			switch (order)
			{
				case DATE_ASC:
					query.setOrdering("created asc");
					break;
				case DATE_DESC:
					query.setOrdering("created desc");
					break;
			}
			List<UserGroup> groups = (List<UserGroup>) query.execute(publicity);

			ArrayList<TeamBean> beans = new ArrayList<TeamBean>();
			for (UserGroup item : groups)
			{
				beans.add(item.toBean());
			}

			return beans;
		}
		finally
		{
			query.closeAll();

			pm.close();
		}
	}

	@Override
	public PagingResult<TeamBean> getTeams(String nameFilter, boolean publicity, EnumTeamOrdering order, String cursor, int pageSize) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(UserGroup.class);
		try
		{
			String filterString = "isPublic == :p1";
			filterString += " && status == '" + EnumTeamStatus.ACTIVE + "'";

			// paging...
			int totalCount = -1;
			if (cursor != null && !cursor.isEmpty())
			{
				Cursor curs = Cursor.fromWebSafeString(cursor);
				Map<String, Object> extensionMap = new HashMap<String, Object>();
				extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, curs);
				query.setExtensions(extensionMap);
			}
			else
			{
				// query for a size (only 1st time)
				Query sizeq = pm.newQuery("select key from " + UserGroup.class.getName());
				sizeq.setFilter(filterString);
				List<Key> keys = (List<Key>) sizeq.execute(publicity);
				totalCount = keys.size();
				sizeq.closeAll();
			}
			query.setRange(0, pageSize);

			switch (order)
			{
				case DATE_ASC:
					query.setOrdering("created asc");
					break;
				case DATE_DESC:
					query.setOrdering("created desc");
					break;
			}
			query.setFilter(filterString);
			List<UserGroup> qres = (List<UserGroup>) query.execute(publicity);

			// paging...
			String newCursorString = "";
			if (qres.size() == pageSize)
			{
				Cursor curs = JDOCursorHelper.getCursor(qres);
				newCursorString = curs.toWebSafeString();
			}

			ArrayList<TeamBean> beans = new ArrayList<TeamBean>();
			for (UserGroup item : qres)
			{
				beans.add(item.toBean());
			}

			return new PagingResult<TeamBean>(beans, newCursorString, totalCount);
		}
		finally
		{
			query.closeAll();

			pm.close();
		}
	}

	@Override
	public ArrayList<BetBean> getBets(String userId, EnumBetStatus status) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(Stake.class);
		try
		{
			// get user...
			UserBean user = this.getPlayer(userId);

			String filterString = "parentKey == :p1";
			if (status == EnumBetStatus.ALL || status == null)
			{
				// filterString +=
				// " && (status == OPENED || status == CLOSED || status == ENDED)";
			}
			else
			{
				filterString += " && status == '" + status.toString() + "'";
			}

			query.setFilter(filterString);
			query.setOrdering("date desc");

			List<Stake> qres = (List<Stake>) query.execute(user.getKey());

			// convert to client friendly bean
			ArrayList<BetBean> stakebeans = new ArrayList<BetBean>();
			for (Stake stake : qres)
			{
				stakebeans.add(stake.toBean());
			}

			return stakebeans;
		}
		finally
		{
			query.closeAll();

			pm.close();
		}

	}

	@Override
	public PagingResult<BetBean> getBets(String teamId, String userId, EnumBetStatus status, String cursor, int pageSize) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(Stake.class);
		try
		{
			UserGroup team = extractTeam(teamId, pm);

			UserBean user;
			if (userId == null || userId.isEmpty())
			{
				// get current user...
				// WebetekUser wuser = utils.getUser(pm, team);
				//
				// if (wuser == null)
				// {
				throw new BackendException(EErrorCodes.NOT_TEAM_MEMBER);
				// }
				// user = wuser.toBean();
			}
			else
			{
				user = this.getPlayer(userId);
			}

			String filterString = "parentKey == :p1";
			if (status == EnumBetStatus.ALL || status == null)
			{
				// filterString +=
				// " && (status == OPENED || status == CLOSED || status == ENDED)";
			}
			else
			{
				filterString += " && status == '" + status.toString() + "'";
			}
			// paging...
			int totalCount = -1;
			if (cursor != null && !cursor.isEmpty())
			{
				Cursor curs = Cursor.fromWebSafeString(cursor);
				Map<String, Object> extensionMap = new HashMap<String, Object>();
				extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, curs);
				query.setExtensions(extensionMap);
			}
			else
			{
				// query for a size (only 1st time)
				Query sizeq = pm.newQuery("select id from " + Stake.class.getName());
				sizeq.setFilter(filterString);
				List<Key> keys = (List<Key>) sizeq.execute(user.getKey());
				totalCount = keys.size();
				sizeq.closeAll();
			}
			query.setRange(0, pageSize);
			query.setFilter(filterString);
			// TODO implement bet ordering...
			query.setOrdering("date desc");

			List<Stake> qres = (List<Stake>) query.execute(user.getKey());

			// paging...
			String newCursorString = "";
			if (qres.size() == pageSize)
			{
				Cursor curs = JDOCursorHelper.getCursor(qres);
				newCursorString = curs.toWebSafeString();
			}

			// convert to client friendly bean
			ArrayList<BetBean> stakebeans = new ArrayList<BetBean>();
			for (Stake stake : qres)
			{
				stakebeans.add(stake.toBean());
			}

			return new PagingResult<BetBean>(stakebeans, newCursorString, totalCount);
		}
		finally
		{
			query.closeAll();
			pm.close();
		}
	}

	@Override
	public ArrayList<PaymentBean> getPayments(String userId, EnumPaymentType status, EnumPaymentOrdering order) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(Payment.class);
		try
		{
			Key key = KeyFactory.stringToKey(userId);

			String filterString = "parentKey == :p1";
			if ((status == EnumPaymentType.ALL) || (status == null))
			{
				// filterString +=
				// " && (type == BET || type == EARNINGS || type == PAYMENT || type == PAYOUT)";
			}
			else
			{
				filterString += " && type == '" + status.toString() + "'";
			}
			if (order == null)
			{
				order = EnumPaymentOrdering.DATE_ASC;
			}
			switch (order)
			{
				case DATE_ASC:
					query.setOrdering("created asc");
					break;
				case DATE_DESC:
					query.setOrdering("created desc");
					break;
				case AMOUNT_ASC:
					query.setOrdering("amount asc");
					break;
				case AMOUNT_DESC:
					query.setOrdering("amount desc");
					break;
			}

			query.setFilter(filterString);
			List<Payment> qresult = (List<Payment>) query.execute(key, status);
			//
			ArrayList<PaymentBean> paymentbeans = new ArrayList<PaymentBean>();
			for (Payment payment : qresult)
			{
				paymentbeans.add(payment.toBean());
			}
			return paymentbeans;
		}
		finally
		{
			query.closeAll();
			pm.close();
		}
	}

	@Override
	public PagingResult<PaymentBean> getPayments(String userId, EnumPaymentType status, EnumPaymentOrdering order, String cursor, int pageSize)
			throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(Payment.class);
		try
		{
			Key key = KeyFactory.stringToKey(userId);

			String filterString = "parentKey == :p1";
			if ((status == EnumPaymentType.ALL) || (status == null))
			{
				// filterString +=
				// " && (type == BET || type == EARNINGS || type == PAYMENT || type == PAYOUT)";
			}
			else
			{
				filterString += " && type == '" + status.toString() + "'";
			}
			// paging...
			int totalCount = -1;
			if (cursor != null && !cursor.isEmpty())
			{
				Cursor curs = Cursor.fromWebSafeString(cursor);
				Map<String, Object> extensionMap = new HashMap<String, Object>();
				extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, curs);
				query.setExtensions(extensionMap);
			}
			else
			{
				// query for a size (only 1st time)
				Query sizeq = pm.newQuery("select key from " + Payment.class.getName());
				sizeq.setFilter(filterString);
				List<Key> keys = (List<Key>) sizeq.execute(key);
				totalCount = keys.size();
				sizeq.closeAll();
			}
			query.setRange(0, pageSize);

			switch (order)
			{
				case DATE_ASC:
					query.setOrdering("created asc");
					break;
				case DATE_DESC:
					query.setOrdering("created desc");
					break;
				case AMOUNT_ASC:
					query.setOrdering("amount asc");
					break;
				case AMOUNT_DESC:
					query.setOrdering("amount desc");
					break;
			}
			query.setFilter(filterString);
			List<Payment> qres = (List<Payment>) query.execute(key);

			// paging...
			String newCursorString = "";
			if (qres.size() == pageSize)
			{
				Cursor curs = JDOCursorHelper.getCursor(qres);
				newCursorString = curs.toWebSafeString();
			}

			//
			ArrayList<PaymentBean> paymentbeans = new ArrayList<PaymentBean>();
			for (Payment payment : qres)
			{
				paymentbeans.add(payment.toBean());
			}
			return new PagingResult<PaymentBean>(paymentbeans, newCursorString, totalCount);
		}
		finally
		{
			query.closeAll();
			pm.close();
		}

	}

	@Override
	public ArrayList<LabelBean> getLabels(String teamId) throws BackendException
	{
		ArrayList<LabelBean> labels = new ArrayList<LabelBean>();

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{

			UserGroup team = extractTeam(teamId, pm);
			// get labels...
			for (BetLabel item : team.getLabels())
			{
				labels.add(item.toBean());
			}

			return labels;
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public boolean deleteEvent(String id) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(id);
			Bet event = pm.getObjectById(Bet.class, key);

			if (event.getStatus() != EnumEventStatus.ACTIVE)
			{
				throw new BackendException(EErrorCodes.WRONG_BET_STATUS);
			}

			// TODO check this
			// if (bet != null)
			// {
			// bet.setActiveUntil(new Date());
			// }
			pm.deletePersistent(event);
		}
		finally
		{
			pm.close();
		}
		return true;
	}

	@Override
	public boolean deletePlayer(String id) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{

			Key key = KeyFactory.stringToKey(id);
			WebetekUser player = pm.getObjectById(WebetekUser.class, key);

			// UserGroup team = player.getParent();
			UserGroup team = pm.getObjectById(UserGroup.class, key.getParent());
			// owner check
			if (key.equals(team.getOwnerKey()))
			{
				throw new BackendException(EErrorCodes.DO_NOT_REMOVE_OWNER);
			}

			team.removePlayer(player);

			return true;
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public boolean deleteTeam(String id)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Key key = KeyFactory.stringToKey(id);

		try
		{
			UserGroup ug = pm.getObjectById(UserGroup.class, key);
			pm.deletePersistent(ug);

			return true;
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public boolean deleteLabel(String id) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();

		Key key = KeyFactory.stringToKey(id);
		Transaction tx = pm.currentTransaction();

		try
		{
			tx.begin();
			BetLabel label = pm.getObjectById(BetLabel.class, key);

			UserGroup team = pm.getObjectById(UserGroup.class, label.getKey().getParent());
			// TODO remove from bets...
			// TODO query bets...
			// for (Bet bet : team.getBets())
			// {
			// List<Key> labels = bet.getLabels();
			// labels.remove(label.getKey());
			// }

			// delete label!
			// pm.deletePersistent(label);
			team.removeLabel(label);

			tx.commit();

			return true;
		}
		finally
		{
			if (tx.isActive())
			{
				tx.rollback();
			}
			pm.close();
		}
	}

	@Override
	public EventBean modifyEvent(EventBean ibet) throws BackendException
	{
		String id = ibet.getKey();
		Date date = ibet.getDate();
		String title = ibet.getTitle();
		String description = ibet.getDescription();
		ArrayList<PickBean> pickbeans = ibet.getPicks();
		ArrayList<String> labels = ibet.getLabels();

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(id);
			Bet bet = pm.getObjectById(Bet.class, key);

			// TODO check membership
			// if (!isTeamEditor(pm, bet.getParent()))
			// {
			// throw new BackendException(EErrorCodes.NOT_TEAM_EDITOR);
			// }

			// update object...
			bet.setDate(date);
			bet.setDescription(description);
			bet.setTitle(title);

			for (Pick pick : bet.getPicks())
			{
				PickBean pb = null;
				for (PickBean item : pickbeans)
				{
					if (item.getId().equals(pick.getSid()))
					{
						pb = item;
						break;
					}
				}
				if (pb != null)
				{
					pick.setTitle(pb.getTitle());
				}

			}

			if (labels != null)
			{
				List<Key> betlabels = new ArrayList<Key>();
				for (String labelkey : labels)
				{
					Key lkey = KeyFactory.stringToKey(labelkey);
					betlabels.add(lkey);
				}
				bet.setLabels(betlabels);
			}

			Date currentTime = new Date(System.currentTimeMillis());
			// check new bet date...
			if (bet.getDate().before(currentTime))
			{
				// check if result assigned..
				if (bet.getStatus() == EnumEventStatus.ACTIVE)
				{
					bet.setStatus(EnumEventStatus.WAITINGRESULTS);
				}
			}
			else if (bet.getDate().after(currentTime))
			{
				if (bet.getStatus() == EnumEventStatus.WAITINGRESULTS)
				{
					bet.setStatus(EnumEventStatus.ACTIVE);
				}
			}

			return bet.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public UserBean modifyPlayer(UserBean user) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(user.getKey());
			WebetekUser player = pm.getObjectById(WebetekUser.class, key);

			// assign new team owner
			if (user.getRole() != player.getRole() && user.getRole() == EnumUserRole.EDITOR)
			{
				throw new BackendException(EErrorCodes.DO_NOT_REMOVE_OWNER);
				// changeTeamOwner(user.getEmail(),
				// pm.getObjectById(UserGroup.class, key.getParent()));
			}
			// do not allow change team owner
			else if (user.getRole() != player.getRole() && player.getRole() == EnumUserRole.EDITOR)
			{
				// changeTeamOwner(user.getEmail(),
				// pm.getObjectById(UserGroup.class, key.getParent()));
				throw new BackendException(EErrorCodes.DO_NOT_REMOVE_OWNER);
			}

			player.setStatus(user.getStatus());
			player.setRole(user.getRole());

			// if (userService.isUserAdmin())
			// {
			// player.setBalance(user.getBalance());
			// }

			return player.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public TeamBean modifyTeam(TeamBean bean) throws BackendException
	{

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			UserGroup team = extractTeam(bean.getId(), pm);

			changeTeamOwner(bean.getOwnerEmail(), team);

			// update object...
			team.setName(bean.getName());
			team.setPublic(bean.isPublic());
			team.setDescription(bean.getDescription());
			team.setTeamRules(bean.getTeamRules());
			team.setMinimumStake(bean.getMinStake());
			team.setMaximumStake(bean.getMaxStake());
			team.setPlaceBetNotifications(bean.isNotifications());

			return team.toBean();

		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public LabelBean modifyLabel(LabelBean bean) throws BackendException
	{

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			BetLabel label = pm.getObjectById(BetLabel.class, bean.getId());

			// update object...
			label.setName(bean.getTitle());

			return label.toBean();

		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public EventBean createEvent(String teamid, String title, String description, Date date, List<PickBean> picks, EnumEventType betType, String labelId)
			throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			// check date
			if (date.before(new Date(System.currentTimeMillis() + GAME_TIME_FUSE_CONST)))
			{
				throw new BackendException(EErrorCodes.BET_DATE_EXCEEDED);
			}

			// check if number of picks corresponds bet type
			if (picks.size() == 0 || picks.size() > MAX_PICKS)
			{
				throw new BackendException(EErrorCodes.INVALID_NO_OF_PICKS);
			}

			Bet bet = new Bet(date, title, description);
			//
			UserGroup team = extractTeam(teamid, pm);
			team.addEvent(bet);

			// add picks...
			for (PickBean item : picks)
			{
				Pick pick = new Pick(item.getTitle());
				bet.addPick(pick);
			}

			try
			{
				if (labelId != null && !labelId.isEmpty())
				{
					BetLabel label = pm.getObjectById(BetLabel.class, labelId);// team.findLabel(labelTitle);
					if (label != null)
					{
						bet.addLabel(label.getKey());
					}
				}
			}
			catch (JDOObjectNotFoundException e)
			{
				e.printStackTrace();
			}

			return bet.toBean();
		}
		finally
		{
			pm.close();
		}

	}

	@Override
	public TeamBean createTeam(String name, String description, String teamRules, String ownerEmail, boolean publicity, boolean notifications, Double minStake,
			Double maxStake) throws BackendException
	{
		if (ownerEmail == null || ownerEmail.isEmpty())
		{
			throw new BackendException(EErrorCodes.INVALID_OWNER_DATA);
		}

		// create team...
		UserGroup team = new UserGroup(name);
		team.setPublic(publicity);
		team.setDescription(description);
		team.setPlaceBetNotifications(notifications);
		team.setTeamRules(teamRules);
		team.setStatus(EnumTeamStatus.WAITINGFORAPPROVAL);
		if (minStake == null)
		{
			team.setMinimumStake(0.0);
		}
		else
		{
			team.setMinimumStake(minStake);
		}
		if (maxStake == null)
		{
			team.setMaximumStake(Double.MAX_VALUE);
		}
		else
		{
			team.setMaximumStake(maxStake);
		}

		PersistenceManager pm = PMF.get().getPersistenceManager();

		try
		{
			// create owner/editor
			WebetekUser owner = new WebetekUser(ownerEmail);
			owner.setRole(EnumUserRole.EDITOR);
			owner.setStatus(EnumUserStatus.ACTIVE);
			team.addPlayer(owner);

			pm.makePersistent(team);

			team.setOwnerKey(owner.getKey(), owner.getEmail());
			
			return team.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public UserBean createPlayer(String teamId, String email, EnumUserStatus status, EnumUserRole role, double initialBalance) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();

		Transaction tx = pm.currentTransaction();
		try
		{
			tx.begin();
			WebetekUser user = new WebetekUser(email);
			user.setRole(role);
			user.setStatus(status);
			if (initialBalance != 0)
			{
				Payment payment = new Payment(initialBalance, "Editor's deposit (balance= " + user.getBalance() + ")", EnumPaymentType.PAYMENT);
				user.addPayment(payment);
			}
			pm.makePersistent(user);
			tx.commit();

			tx.begin();
			// add to team...
			UserGroup team = extractTeam(teamId, pm);
			if (!team.addPlayer(user))
			{
				throw new BackendException(EErrorCodes.PLAYER_EXISTS_ALREADY);
			}
			tx.commit();

			return user.toBean();
		}
		finally
		{
			if (tx.isActive())
			{
				tx.rollback();
			}
			pm.close();
		}
	}

	@Override
	public PaymentBean createPayment(String userId, String teamId, double amount, EnumPaymentType type) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();

		try
		{
			WebetekUser player = null;
			// Key tkey = KeyFactory.stringToKey(teamId);
			// UserGroup team = pm.getObjectById(UserGroup.class, tkey);

			Key ukey = KeyFactory.stringToKey(userId);
			player = pm.getObjectById(WebetekUser.class, ukey);

			// WebetekUser user = utils.getUser(pm, userService,
			// player.getParent());

			// set balance
			// double curbal = player.getBalance();
			// player.setBalance(curbal + amount);

			Payment payment = new Payment(amount, "Editor's deposit (balance= " + player.getBalance() + ")", type);
			player.addPayment(payment);

			return payment.toBean();

		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public BetBean createBet(String userId, String teamId, double totalAmount, List<String> pickKeyStrings, EnumBetType type) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();

		// Transaction tx = pm.currentTransaction();
		try
		{

			// tx.begin();
			WebetekUser user = extractUser(userId, pm);

			double curBal = user.getBalance();
			// check balance and limits
			if ((curBal - totalAmount) < user.getLimit())
			{
				throw new BackendException(EErrorCodes.STAKE_BALANCE_EXCEEDED);
			}

			// check min/max
			UserGroup team = extractTeam(teamId, pm);
			Double min = team.getMinimumStake() * pickKeyStrings.size();
			Double max = team.getMaximumStake() * pickKeyStrings.size();
			if (totalAmount < min || totalAmount > max)
			{
				throw new BackendException(EErrorCodes.STAKE_LIMIT_EXCEEDED);
			}

			double odds = 0.0;
			// convert keys...
			List<Key> pickKeys = new ArrayList<Key>();
			for (String item : pickKeyStrings)
			{
				Key key = KeyFactory.stringToKey(item);
				pickKeys.add(key);

				// calculate totalOdds
				Pick pick = pm.getObjectById(Pick.class, key);
				if (type == EnumBetType.SINGLE)
				{
					odds += pick.getOdds();
				}
				else
				{
					odds *= pick.getOdds();
				}
			}
			// check totalOdds
			if (odds <= 0.0)
			{
				throw new BackendException(EErrorCodes.WRONG_ODDS_CALCULATIONS);
			}

			// create user bet
			Stake stake = new Stake(totalAmount, pickKeys, type);
			stake.setTotalOdds(odds);
			user.addStake(stake);
			// and handle bet payment...
			Payment pay = new Payment(-totalAmount, "Bet placed (balance=" + user.getBalance() + ")", EnumPaymentType.BET);
			user.addPayment(pay);
			// tx.commit();

			return stake.toBean();
		}
		catch (IllegalArgumentException e)
		{
			throw new BackendException(EErrorCodes.INVALID_ID);
		}
		finally
		{
			// if (tx.isActive())
			// {
			// tx.rollback();
			// }
			pm.close();
		}
	}

	@Override
	public LabelBean createLabel(String teamId, String title) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			UserGroup team = extractTeam(teamId, pm);

			BetLabel label = new BetLabel(title);
			team.addLabel(label);

			// bet.addLabel(label.getKey());

			return label.toBean();
		}
		finally
		{
			pm.close();
		}
	}

	@Override
	public List<String> getPicksForEvent(String userId, String betId) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(Stake.class);

		try
		{
			Key eventKey = KeyFactory.stringToKey(betId);
			Bet event = pm.getObjectById(Bet.class, eventKey);

			Key userKey = KeyFactory.stringToKey(userId);

			List<Pick> picks = event.getPicks();
			List<Key> pickKeys = new ArrayList<Key>();
			for (Pick item : picks)
			{
				pickKeys.add(item.getId());
			}
			//
			String filterString = "parentKey == :p1 && pickKeys == :p2";
			query.setFilter(filterString);

			List<Stake> qres = (List<Stake>) query.execute(userKey, pickKeys);

			List<String> result = new ArrayList<String>();
			for (Stake item : qres)
			{
				List<Key> keys = item.getPickKeys();
				for (Key kitem : keys)
				{
					if (pickKeys.contains(kitem))
					{
						String kstr = KeyFactory.keyToString(kitem);
						result.add(kstr);
					}
				}

			}

			return result;
		}
		finally
		{
			query.closeAll();
			pm.close();
		}
	}

	@Override
	public EventBean assignResultPick(String pickkey, String comment)
	{

		Key winkey = KeyFactory.stringToKey(pickkey);

		PersistenceManager pm = PMF.get().getPersistenceManager();

		try
		{
			Bet event = pm.getObjectById(Bet.class, winkey.getParent());

			// set pick status
			List<Pick> picks = event.getPicks();
			for (Pick item : picks)
			{
				if (item.getId().equals(winkey))
				{
					// apply PICK status
					item.setStatus(EnumPickStatus.WON);
				}
				else
				{
					// apply PICK status
					item.setStatus(EnumPickStatus.LOOSE);
				}
			}

			// assign result comment and status
			event.setResultDescription(comment);
			event.setStatus(EnumEventStatus.CLOSED);

			return event.toBean();
		}
		finally
		{

			pm.close();
		}

	}

	@Override
	public List<UserBean> completeStakes(String eventkey)
	{
		Key eventKey = KeyFactory.stringToKey(eventkey);

		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			List<UserBean> users = new ArrayList<UserBean>();

			// TODO optimization - retrieve keys only
			Bet event = pm.getObjectById(Bet.class, eventKey);
			for (Pick item : event.getPicks())
			{
				users.addAll(completeStakesForPick(item.getId()));
			}

			return users;
		}
		finally
		{
			pm.close();
		}

	}

	private List<UserBean> completeStakesForPick(Key resultKey)
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();

		Query query = pm.newQuery(Stake.class);
		try
		{
			//
			String filterString = "pickKeys == :p2 && status == '" + EnumBetStatus.OPENED + "'";
			query.setFilter(filterString);
			List<Stake> qres = (List<Stake>) query.execute(resultKey);

			// TODO consider using helper method instead...
			List<UserBean> users = new ArrayList<UserBean>();
			for (Stake bet : qres)
			{
				// traverse all picks...
				boolean betIncomplete = false;
				double earnings = 0.0;
				int wonCnt = 0;
				List<Key> keys = bet.getPickKeys();
				double amount = bet.getAmount() / keys.size();
				for (Key key : keys)
				{
					Pick pick = pm.getObjectById(Pick.class, key);
					if (pick.getStatus() == EnumPickStatus.NONE)
					{
						// not all bets (picks) closed...
						// reset earnings...
						bet.setEarnings(0.0);
						betIncomplete = true;
						break;
					}
					else if (pick.getStatus() == EnumPickStatus.WON)
					{
						// TODO problem - should be: bet.getAmount() /
						// picks.size()
						earnings += pick.getOdds() * amount;
						wonCnt++;
					}

				}

				// check other picks
				if (betIncomplete == false)
				{
					// TODO error handling...
					if (Double.isInfinite(earnings) || Double.isNaN(earnings) || earnings < 0)
					{
						// log.severe("Wrong Stake earnings calculation!!! amount="
						// + stake.getAmount());
					}
					else
					{
						bet.setWonCnt(wonCnt);
						bet.setEarnings(earnings);
						// some stats...
						WebetekUser user = pm.getObjectById(WebetekUser.class, bet.getId().getParent());
						user.updateWonBetsCount(wonCnt);
						user.updateAllBetCount(keys.size());

						// update user balance & earnings...
						if (earnings > 0.0)
						{

							Payment payment = new Payment(earnings, "Earnings calculation (balance=" + user.getBalance() + ")", EnumPaymentType.EARNINGS);
							user.addPayment(payment);

							// collect users for response
							users.add(user.toBean());
						}
					}

					// update stake
					bet.setStatus(EnumBetStatus.CLOSED);
				}

			}

			return users;
		}
		finally
		{
			query.closeAll();
			pm.close();

		}
	}

	@Override
	public boolean deletePayment(String id) throws BackendException
	{

		PersistenceManager pm = PMF.get().getPersistenceManager();
		Key key = KeyFactory.stringToKey(id);

		try
		{
			Payment payment = pm.getObjectById(Payment.class, key);
			WebetekUser user = payment.getParent(pm);

			// storno positive payments only...
			if (payment.getAmount() > 0)
			{
				Payment storno = new Payment(payment.getAmount() * (-1), "[STORNO] " + payment.getDescription(), payment.getType());
				storno.setType(EnumPaymentType.STORNO);
				user.addPayment(storno);
				// change status of original payment
				payment.setType(EnumPaymentType.INVALID);
			}

			return true;
		}
		finally
		{
			pm.close();
		}

	}

	@Override
	public PickBean modifyPick(PickBean bean) throws BackendException
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public PickBean getPick(String pickId) throws BackendException
	{
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try
		{
			Key key = KeyFactory.stringToKey(pickId);
			Pick group = pm.getObjectById(Pick.class, key);

			Bet event = pm.getObjectById(Bet.class, key.getParent());

			return group.toBean(event.toBean());
		}
		finally
		{
			pm.close();
		}
	}
}
